import datetime
from enum import Enum
from typing import List
from peewee import *

db = DatabaseProxy()

### Database Models
class BaseDBModel(Model):
    id = AutoField(unique=True)
    class Meta:
        database = db

class Account(BaseDBModel):
    firstName = CharField()
    lastName = CharField()
    email = CharField(unique=True) 
    password = CharField()

class RegionType(BaseDBModel):
    type = CharField(unique=True)
    

class OsmData(BaseDBModel):
    mapLink = CharField()
    boundingBox_x1 = FloatField()
    boundingBox_y1 = FloatField()
    boundingBox_x2 = FloatField()
    boundingBox_y2 = FloatField()
    
class POI(BaseDBModel):
    name = CharField()
    type = CharField()
    latitude = FloatField()
    longitude = FloatField()
    osm_id = ForeignKeyField(OsmData, field="id", null=True, backref="pois")
    
class Region(BaseDBModel):
    name = CharField(unique=True)
    parentRegion = ForeignKeyField('self', null=True, field="id", backref="subregions")
    regionType = ForeignKeyField(RegionType, field="id", backref="regions", null=True)
    latitude = FloatField()
    longitude = FloatField()
    accountId = ForeignKeyField(Account, field="id", backref="regions")
    osmData = ForeignKeyField(OsmData, field="id")
    

class Forecast(BaseDBModel):
    regionId = ForeignKeyField(Region, to_field="id", backref="forecasts")
    temperature = FloatField()
    weatherCondition = CharField()
    dateTime = DateTimeField()

class Weather(BaseDBModel):
    regionId = ForeignKeyField(Region, field="id", backref="weathers", null=True)
    temperature = FloatField()
    humidity = FloatField()
    precipitationAmount = FloatField()
    windSpeed = FloatField()
    weatherCondition = CharField()
    measurementDateTime = DateTimeField()
    forecasts = ManyToManyField(Forecast, backref="weathers")

    

db_models = [Account, RegionType, OsmData, POI, Region, Forecast, Weather, Weather.forecasts.get_through_model()]

### Pydantic Models
import pydantic

class RegisterUser(pydantic.BaseModel):
    email: str
    password: str
    firstName: str
    lastName: str

class LoginUser(pydantic.BaseModel):
    email: str
    password: str

class UserLoginResponse(pydantic.BaseModel):
    id: int

    def from_db(user: Account):
        return UserLoginResponse(id=user.id)

class UserResponse(pydantic.BaseModel):
    id: int
    firstName: str
    lastName: str
    email: str

    def from_db(user: Account):
        ur = UserResponse(
            id=user.id, 
            email=user.email, 
            firstName=user.firstName, 
            lastName=user.lastName
            )
        return ur

class POIResponse(pydantic.BaseModel):
    name: str
    type: str
    latitude: float
    longitude: float
    
    def from_db(poi: POI):
        pr = POIResponse(
            name=poi.name,
            type=poi.type,
            latitude=poi.latitude,
            longitude=poi.longitude
        )
        return pr

class OsmDataResponse(pydantic.BaseModel):
    mapLink: str
    boundingBox: List[float]
    pois: List[POIResponse]

    def from_db(osmData: OsmData):
        pois = [POIResponse.from_db(poi) for poi in osmData.pois]
        odr = OsmDataResponse(
            mapLink=osmData.mapLink,
            boundingBox=[osmData.boundingBox_x1, osmData.boundingBox_y1, 
                         osmData.boundingBox_x2, osmData.boundingBox_y2],
            pois=pois
        )
        return odr

class RegionResponse(pydantic.BaseModel):
    id: int
    name: str
    parentRegion: str | None
    regionType: int | None
    latitude: float
    longitude: float

    def from_db(region: Region):
        rr = RegionResponse(
            id=region.id,
            name=region.name,
            parentRegion=region.parentRegion.name if region.parentRegion else "",
            regionType=region.regionType.id if region.regionType else None,
            latitude=region.latitude,
            longitude=region.longitude
        )
        return rr
    
class RegionGetResponse(pydantic.BaseModel):
    id: int
    name: str
    accountId: int
    parentRegion: str | None
    regionType: List[int] | None
    latitude: float
    longitude: float
    osmData: OsmDataResponse | None

    def from_db(region: Region):
        rr = RegionGetResponse(
            id=region.id,
            name=region.name,
            accountId=region.accountId.id,
            parentRegion=region.parentRegion.name if region.parentRegion else "",
            regionType=[rt.id for rt in region.regionType] if region.regionType else None,
            latitude=region.latitude,
            longitude=region.longitude,
            osmData=OsmDataResponse.from_db(region.osmData) if region.osmData else None
        )
        return rr

class RegionWeatherDisassignResponse(pydantic.BaseModel):
    id: int
    name: str
    parentRegion: str
    latitude: float
    longitude: float

    def from_db(region: Region):
        rwd = RegionWeatherDisassignResponse(
            id=region.id,
            name=region.name,
            parentRegion=region.parentRegion.name,
            latitude=region.latitude,
            longitude=region.longitude
        )
        return rwd

class OsmDataRequest(pydantic.BaseModel):
    includePOI: bool | None = False
    updatePOI: bool | None = False
    
    def OsmDataRequest():
        return OsmDataRequest(includePOI=False, updatePOI=False)

class RegionRequest(pydantic.BaseModel):
    name: str
    parentRegion: str | None
    regionType: int | None
    latitude: float
    longitude: float
    latitude1: float
    longitude1: float
    osmData: OsmDataRequest | None = OsmDataRequest()

class RegionTypeRequest(pydantic.BaseModel):
    type: str

class RegionTypeResponse(pydantic.BaseModel):
    id: int
    type: str

    def from_db(regionType: RegionType):
        rt = RegionTypeResponse(
            id=regionType.id,
            type=regionType.type
        )
        return rt

class WeatherCondition(str, Enum):
    clear = "CLEAR"
    cloudy = "CLOUDY"
    rain = "RAIN"
    snow = "SNOW"
    fog = "FOG"
    storm = "STORM"

class ForecastRequest(pydantic.BaseModel):
    regionId: int
    temperature: float
    weatherCondition: WeatherCondition
    dateTime: datetime.datetime = datetime.datetime.now()

class ForecastResponse(pydantic.BaseModel):
    id: int
    regionId: int
    temperature: float
    weatherCondition: WeatherCondition
    dateTime: datetime.datetime

    def from_db(forecast: Forecast):
        fr = ForecastResponse(
            id=forecast.id,
            regionId=forecast.regionId.id,
            temperature=forecast.temperature,
            weatherCondition=forecast.weatherCondition,
            dateTime=forecast.dateTime
        )
        return fr
class ForecastWeatherGetResponse(pydantic.BaseModel):
    forecastId: int
    dateTime: datetime.datetime
    temperature: float
    weatherCondition: WeatherCondition
    precipitationAmount: float
    windSpeed: float

    # откуда я должен взять precipitationAmount и windSpeed в прогнозе?
    def from_db(forecast: Forecast, precipitationAmount: float, windSpeed: float):
        fr = ForecastWeatherGetResponse(
            forecastId=forecast.id,
            temperature=forecast.temperature,
            weatherCondition=forecast.weatherCondition,
            dateTime=forecast.dateTime,
            precipitationAmount=precipitationAmount,
            windSpeed=windSpeed
        )
        return fr

class WeatherRequest(pydantic.BaseModel):
    regionId: int
    temperature: float
    humidity: float
    precipitationAmount: float
    windSpeed: float
    weatherCondition: WeatherCondition
    measurementDateTime: str
    weatherForecast: List[int] = []

class WeatherResponse(pydantic.BaseModel):
    id: int
    regionName: str
    temperature: float
    humidity: float
    precipitationAmount: float
    windSpeed: float
    weatherCondition: WeatherCondition
    measurementDateTime: datetime.datetime
    weatherForecast: List[ForecastWeatherGetResponse] = []
    osmLink: str

    def from_db(weather: Weather):
        wr = WeatherResponse(
            id=weather.id,
            regionName=weather.regionId.name,
            temperature=weather.temperature,
            humidity=weather.humidity,
            precipitationAmount=weather.precipitationAmount,
            windSpeed=weather.windSpeed,
            weatherCondition=weather.weatherCondition,
            measurementDateTime=weather.measurementDateTime,
            weatherForecast=[ForecastWeatherGetResponse.
                             from_db(f, 
                                    weather.precipitationAmount, 
                                    weather.windSpeed) for f in weather.forecasts]
        )
        return wr

class WeatherPostResponse(pydantic.BaseModel):
    id: int
    temperature: float
    humidity: float
    precipitationAmount: float
    windSpeed: float
    weatherCondition: WeatherCondition
    measurementDateTime: datetime.datetime
    weatherForecast: List[int] = []

    def from_db(weather: Weather):
        wr = WeatherPostResponse(
            id=weather.id,
            temperature=weather.temperature,
            humidity=weather.humidity,
            precipitationAmount=weather.precipitationAmount,
            windSpeed=weather.windSpeed,
            weatherCondition=weather.weatherCondition,
            measurementDateTime=weather.measurementDateTime,
            weatherForecast=[f.id for f in weather.forecasts]
        )
        return wr


class AssignWeatherToRegionResponse(WeatherResponse):
    regionName: str

    def from_db(weather: Weather):
        awtr = AssignWeatherToRegionResponse(
            id=weather.id,
            regionName=weather.regionId.name,
            temperature=weather.temperature,
            humidity=weather.humidity,
            precipitationAmount=weather.precipitationAmount,
            windSpeed=weather.windSpeed,
            weatherCondition=weather.weatherCondition,
            measurementDateTime=weather.measurementDateTime.isoformat(),
            weatherForecast=[f.id for f in weather.forecasts]
        )
        return awtr

# Security
class Token(pydantic.BaseModel):
    access_token: str
    token_type: str


class TokenData(pydantic.BaseModel):
    id: int | None = None

